/**
 * Copyright 2008 Matthew DeLambo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.delambo.drool.pooling;

import com.delambo.drool.interfaces.*;
import com.delambo.drool.utilities.*;
import java.sql.*;
import java.util.*;

/**
 * The central object that takes care of pooling logic for a jdbc pools.
 * @author delambo
 * @param NODE type of pool node.
 * @param R, type of node resource.
 */
public final class Pool_Impl<NODE extends PoolNode, R> 
        implements Pool<NODE, R> {
    final private AtomicLockingArray<NODE> pool;
    final private DataSource ds;
    final private Logger logger;
    final private boolean autoCommit;
    final private CleanerThread cleanerThread;
    final private PoolNode.Factory pnFactory;
    final private Resource.Factory resourceFactory;
    final private CircularIndexPointer indexPtr;

    public Pool_Impl(AtomicLockingArray ala, boolean autoCommit, DataSource ds, 
            CleanerThread ct, Logger logger, Resource.Factory resourceFactory,
            PoolNode.Factory pnFactory, CircularIndexPointer cip) {
        this.pool = ala;
        this.autoCommit = autoCommit;
        this.ds = ds;
        this.cleanerThread = ct;
        this.logger = logger;
        this.pnFactory = pnFactory;
        this.resourceFactory = resourceFactory;
        this.indexPtr = cip;
    }
    
    /**
     * Returns a resource to this pool by expiring its node's lease.
     * @param resource borrowed resource to return to node.
     */
    public void returnResource(R resource) {
        if(resource == null) 
            printState("NULL received when returning a resource to the pool: ");
        else {
            int pool_size = pool.getSize();
            for(int index = 0; index < pool_size; index++) {
                NODE node = pool.getNode(index);
                if(node != null && node.getResource() == resource) {
                    node.expireLease();
                    break;
                }
            }
        }
    }
    
    /**
     * Returns the next available/leasable resource from the pool. Pool nodes
     * are not thread-safe because they are also modified and shared by the 
     * CleanerThread_Impl.cleanNodes(), therefore locking is used. 
     * @return next resource in the pool that is not inuse.
     */
    public R getResource() {
        int size = pool.getSize();
        int index = this.indexPtr.getAndIncrement();
        for(int tries = 0; tries++ < size; index = ++index >= size ? 0 : index) {
            boolean gotlock = false;
            try {
                if(gotlock = pool.tryLock(index)) {
                    NODE node = pool.getNode(index);
                    if(nodeIsLeasable(node)) {
                        node.lease();
                        return (R) node.getResource();
                    }
                } 
            } finally {
                if(gotlock) 
                    pool.unlock(index);
            }
        }
        return null;
    }
    
    /**
     * Closes nodes in pool, unloads the driver, and cancels cleaning thread.
     */
    public void closePool() {
        for(int index = 0; index < pool.getSize(); index++) {
            NODE node = pool.getNode(index);
            if(node != null) {
                node.killResource();
                node = null;
            }
        }
        if(ds != null)
            ds.unLoadDriver();
        if(cleanerThread != null)
            cleanerThread.cancel();
    }
    
    /**
     * Prints the current state of the pool.
     * @param location location or info about callee.
     */
    public void printState(String location) {
        logger.dev(getState(location), null);
    }  
    
    /**
     * Starts the pool by initializing nodes and the cleaner thread.
     */
    public void startPool() {
        initNodes();
        cleanerThread.startTimer();
    }
    
    /**
     * Returns the result for whether or not a node's resource can be leased.
     * @param node node to be checked.
     * @return true if node is not null, inuse, and its resource validates;
     *      otherwise, false.
     */
    private boolean nodeIsLeasable(NODE node) {
        return node != null && !node.inUse() && node.resourceValidates();
    }
    
    /**
     * Initializes the pool nodes and resources.
     */
    private void initNodes() {
        for(int index = 0; index < pool.getSize(); index++) {
            NODE node = (NODE) pnFactory.create(
                    resourceFactory.create(ds.get(), autoCommit));
            node.init();
            pool.setNode(index, node);
            pool.unlock(index);
        }
    }
    
    /**
     * Returns a string of the current state of the pool.
     * @param location where the state was requested
     * @return state of pool
     */
    private String getState(String location) {
        String newline = System.getProperty("line.separator");
        StringBuffer sb = new StringBuffer();
        sb.append(newline + "Location: " + location);
        sb.append(newline + "Pool Size: " + pool.getSize());
        for(int i = 0; i < pool.getSize(); i++) {
            sb.append(newline + "\tConnection " + i + ": " 
                    + pool.getNode(i).getResource());
            sb.append(newline + "\t\tLocked: " + pool.locked(i));
            sb.append("\tLease Time: " + pool.getNode(i).getLeaseTime());
            sb.append("\tIn Use: " + pool.getNode(i).inUse());
        }
        return sb.toString();
    }
}